Visualising micromagnetic simulation data with Holoviews

In micromagnetic simulations, it is common to want to investigate systems through a multidimensional parameter space. We may wish to see how a system evolves through time, or how adjusting the strength of interactions might change the final state. In this notebook, we show how output files from OOMMF can be displayed using Holoviews, and how this can allow interactive exploration of data.

In [1]:
import sys
sys.version
Out[1]:
'3.5.2 | packaged by conda-forge | (default, Jun 30 2016, 21:22:36) \n[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.54)]'
In [2]:
%load_ext autoreload
%autoreload 2
In [3]:
import holoviews as hv
import pandas as pd
import numpy as np
import oommffield
import oommfodt
import os
import glob
import re
import sys
sys.path.append('../')
datafiles = os.path.join(os.getcwd(), 'data')
hv.notebook_extension('matplotlib')

import joommftools
HoloViewsJS, MatplotlibJS successfully loaded in this cell.

We can load up an OMF file from an OOMMF simulation of a cube of dimensions [5e-8, 5e-8, 5e-8] nm to test out some of the functions in joommftools. Here, the simulation files correspond to the output from a hysteresis simulation of a cube, with Exchange and Demagnetisation interactions included.

In [4]:
# Glob through data file directory for *.omf files.
files = sorted(glob.glob(datafiles + '/*.omf'))
# Get holoviews object from the first file
test = joommftools.field2outofplane(files[0], 'y', 0e-8)
In [5]:
test
Out[5]:

We can sample across a holoviews Image element through the centre of the image:

In [6]:
test + test.sample(z = 2.5e-8) + test.sample(z = 2.5e-8)
Out[6]:

We might want to change the colourmap of the B image to a divergent colourmap, and add a colorbar. We can do this by setting properties of the renderer with cell magics. Red-Blue and Grayscale colourmaps are commonly used in micromagnetics, so these are shown here:

In [7]:
%%opts Image style(interpolation='nearest', cmap='RdBu')[colorbar=True]
test
Out[7]:
In [8]:
%%opts Image style(interpolation='nearest', cmap='gray')[colorbar=True]
test
Out[8]:

If we want to set options permanently, we can do it using the cell magic (note the single '%'):

In [9]:
%opts Image style(interpolation='nearest', cmap='RdBu')[colorbar=True]

We can also plot the in-plane magnetisation, the angle of the field in plane, or the topological density of the magnetic field

In [10]:
joommftools.field2outofplane(files[0], 'y', 0) + \
joommftools.field2inplane_vectorfield(files[0], 'y', 0)
Out[10]:
In [11]:
joommftools.field2inplane_vectorfield(files[0], 'y', 0)
Out[11]:
In [12]:
joommftools.field2topological_density(files[0], 'y', 0)
(10, 10)
Out[12]:
In [13]:
joommftools.field2inplane_angle(files[0], 'y', 0)
Out[13]:

With the angle, it may be more sensible to use a cyclic colormap as $\theta = 2\pi$ corresponds to the same angle as $\theta = 0$. We simply create a new colormap that matplotlib can understand, and pass this in the plotting options:

In [14]:
import matplotlib.colors as col
def mkcmap(): 
    blue = '#093FB2'
    red = '#FF2A00'
    green = '#AFFF19'
    anglemap = col.LinearSegmentedColormap.from_list(
        'anglemap', [green, blue, red, green], N=56, gamma=1)
    return anglemap

testmap = mkcmap()
In [15]:
%%opts Image style(interpolation='nearest', cmap=testmap)[colorbar=True]
joommftools.field2inplane_angle(files[0], 'y', 0)
Out[15]:

We have several dimensions through which to explore the data - the files correspond to different simulation parameters (in this case, the applied field), and we may want to look at how the magnetisation varies along each of the axes. To explore the data, we create a Holomap, which will provide sliders and selectors which allow the varying of how we visualise the simulation data.

The first step to this is to construct the list of values we can slice through the file:

In [16]:
slicecoords = np.linspace(0, 5e-8, 11)
slicecoords = list(slicecoords)

We now construct the holoviews objects for each combination across parameter space, and show the representation of this as a HoloMap. HoloMaps are created in advance, and can be saved in HTML documents and shared. However, they take up a large amount of memory, and therefore we only create the map for the first 10 files, and only for the 'Mz' dimension.

In [17]:
inplane = joommftools.create_outofplane_holomap(files[:10], slicecoords, 'z')
inplane
Out[17]:

We can create a similar map for the out of plane magnetisation. Functions for creating maps of the other functions above also exist.

In [18]:
ooplane = joommftools.create_inplane_holomap(files[:10], slicecoords, 'z')
ooplane
Out[18]:

We can combine multiple maps together, and because they share dimensions (i.e, the list of files, and possible coordinate values), HoloViews resolves this for us

In [19]:
inplane + ooplane
Out[19]:

Clearly there is a lot of potential for more advanced exploration, as any function which maps filenames to parameters can be written to create a HoloMap. Examples of other parameters you could explore are:

  • Time
  • System size
  • Applied Field
  • Exchange/DMI/Anisotropy strength
  • Anisotropy Axis

Additionally, other hv objects can be created which can be computed for each slice through the field:

  • Field profile along an axis
  • Skyrmion number
  • Structure factor

Here, we use the same data to create a Dynamic Map. Processing of OOMMF data files happens on the fly rather than in advance. This is a bonus if you have a lot of computing power, or if the amount of data is very large. However, it is slightly less responsive, and these maps cannot be viewed without a live notebook server.

In [20]:
dmap1 = joommftools.create_inplane_dynamic_map(files, slicecoords, 'z')
dmap2 = joommftools.create_outofplane_dynamic_map(files, slicecoords, 'z')

These dynamic maps can be combined just as HoloMaps can:

In [21]:
dmap1 + dmap2
Out[21]:

We can interpret tabular data with Joommftools, from ODT files saved by OOMMF. We can instantiate a class from a path to an ODT file and a list of corresponding OMF files from the same simulation:

In [22]:
odtpath = os.path.join(datafiles, 'cube_example.odt')
odtfile = joommftools.ODT2hv(odtpath, files)

We can plot graphs of the ODT file with the method get_curve. A list of available graphs can be shown by accessing the headers property of the ODT2hv class:

In [23]:
odtfile.headers
Out[23]:
['stage',
 'iteration',
 'max_mxHxm',
 'E',
 'delta_E',
 'bracket_count',
 'line_min_count',
 'conjugate_cycle_count',
 'cycle_count',
 'cycle_sub_count',
 'energy_cal_count',
 'Eex',
 'max_spin_angle',
 'stage_max_spin_angle',
 'run_max_spin_angle',
 'UZeeman::Energy',
 'UZeeman::B',
 'UZeeman::Bx',
 'UZeeman::By',
 'UZeeman::Bz',
 'Ed',
 'stage_iteration',
 'mx',
 'my',
 'mz']

The filename is used as the identifier for a particular graph. The get_curves function adds a verical line to indicate the value of the graph for a given file.

In [28]:
odtfile.get_curve(files[0], 'mz')
Out[28]:

We can create a HoloMap or Dynamic map over these graphs with the method create_holomap() or create_dmap(). Because there are a large number of possible graphs in this example, we have to set a property of HoloViews to render more frames than is allowed by default. Be warned that this cell takes a long time to run! (Note that actually computing the data is fast, it's the rendering of plots that is slow).

In [25]:
%%output max_frames=100000
odtfile.create_holomap()
Out[25]:

Here is the dynamic map:

In [26]:
graphmap = odtfile.create_dmap()
In [27]:
graphmap
Out[27]:

Currently, due to an unidentified bug, this object cannot be combined with the previous maps to show the graphs alongside the magnetisation. From some early diagnosic work, this seems to be a bug in holoviews.core.traversal. where the a function argument is not inferred correctly, as the dimension 'File' is not picked up as being shared